<?php

namespace App\Library\Traits;

use App\Library\Logic\AmountLogic;
use App\Library\Logic\PaymentRelatedLogic;
use App\Library\Logic\ShopOrderReturnLogic;
use App\Library\YsdLib;
use App\Models\PaymentModel;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidParamsException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Artful\Rocket;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;

trait WechatPaymentTrait
{
    /**
     * mini
     * @param Model $order
     * @param string $serialNumber
     * @param string $code
     * @param int $format
     * @return JsonResponse
     * @throws GuzzleException
     */
    public static function mini(Model $order, string $serialNumber, string $code, int $format = 0): JsonResponse
    {
        $key = config('app.wechat.mini_app_id');
        $secret = config('app.wechat.mini_app_secret');
        $client = new Client;
        $url = 'https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code';
        $response = $client->get(sprintf($url, $key, $secret, $code));
        $obj = json_decode($response->getBody()->getContents());
        if (!empty($obj->errcode)) return failMessage($obj->errmsg);
        else if (empty($obj->openid)) return fail(499);
        $array = [
            'out_trade_no' => $serialNumber,
            'description' => $order->title,
            'attach' => $order->getTable(),
            'amount' => ['total' => prod() ? (int)bcmul($order->pay_price, 100) : 1],
            'payer' => ['openid' => $obj->openid],
        ];
        if ($order->getTable() === 'shop_order') {
            $bool = $order->plug > 0 || $order->plug2 > 0;
            $ttl = (int)__('base.' . ($bool ? 'plugCancelTtl' : 'cancelTtl'));
            $array['time_expire'] = now()->parse($order->created_at)->addSeconds($ttl - 5)->format('Y-m-d\TH:i:sP');
        }
        $mini = Pay::wechat()->mini($array);
        if (!empty($mini->code)) return failMessage($mini->message);
        else if ($format === 0) return success([
            'data' => $mini,
            'serialNumber' => $order->serial_number,
            'payPrice' => $order->pay_price,
        ]);
        else return success($mini);
    }

    /**
     * successTrait
     * @param Collection $result
     * @return null|Collection|array
     * @throws ContainerException|InvalidParamsException|ServiceNotFoundException|InvalidParamsException
     */
    public static function successTrait(Collection $result): null|Collection|Rocket
    {
        if ($result->get('event_type') === 'REFUND.SUCCESS') {//退款成功
            payLog($result->get('summary'));
            return null;
        } else if ($result->get('event_type') === 'TRANSACTION.SUCCESS') {//支付成功
            payLog($result->get('summary'));
            $serialNumber = $result->get('resource.ciphertext.out_trade_no');
            if (empty($serialNumber)) {
                payLog(__CLASS__ . __FUNCTION__ . ':参数错误');
                return null;
            }
            $query = Pay::wechat()->query(['out_trade_no' => $serialNumber]);
            if (!empty($query->code)) {
                payLog(__CLASS__ . __FUNCTION__ . '!empty($query->code)');
                return null;
            }
            return $query;
        }
        return null;
    }

    /**
     * refund
     * @param Model $model
     * @return Collection|Rocket|Model
     * @throws ContainerException
     * @throws InvalidParamsException
     * @throws ServiceNotFoundException
     * @throws Exception
     */
    public static function refund(Model $model): Collection|Rocket|Model
    {
        if (!prod() && $model->getTable() === 'shop_order_return') return self::testRefund($model);
        else return self::refundCom($model);
    }

    /**
     * withdrawTest
     * @throws ServiceNotFoundException
     * @throws ContainerException
     * @throws InvalidParamsException|Exception
     */
    public static function withdraw(Model $withdraw): void
    {
        $amount = $withdraw->amount * 100;
        $array = [
            'appid' => config('app.wechat.mini_app_id'),
            'out_batch_no' => $withdraw->serial_number,
            'batch_name' => '提现:' . $withdraw->name,
            'batch_remark' => '提现',
            'total_amount' => $amount,
            'total_num' => 1,
            'transfer_detail_list' => [[
                'out_detail_no' => $withdraw->serial_number,
                'transfer_amount' => $amount,
                'transfer_remark' => '提现',
                'openid' => $withdraw->identity,
            ]],
        ];
        if ($amount >= 30) $array['transfer_detail_list'][0]['user_name'] = $withdraw->name;
        self::withdrawCom($withdraw, Pay::wechat()->transfer($array));
    }

    /**
     * withdrawTest
     * @throws ServiceNotFoundException
     * @throws ContainerException
     * @throws InvalidParamsException|Exception
     */
    public static function withdrawTest(Model $withdraw): void
    {
        self::withdrawCom($withdraw, Pay::wechat()->transfer([
            'appid' => config('app.wechat.mini_app_id'),
            'out_batch_no' => $withdraw->serial_number,
            'batch_name' => '提现test',
            'batch_remark' => '提现test',
            'total_amount' => 10,
            'total_num' => 1,
            'transfer_detail_list' => [
                [
                    'out_detail_no' => $withdraw->serial_number,
                    'transfer_amount' => 10,
                    'transfer_remark' => '提现test',
                    'openid' => 'oKo2b5X3XLJom0PhRlSj23a24NWk',
                ],
            ],
            'transfer_scene_id' => '1001',
            'notify_url' => config('wechat.notify_refund_url'),
        ]));
    }

    /**
     * withdrawCom
     * @param Model $withdraw
     * @param Collection|Rocket $result
     * @return void
     * @throws Exception
     */
    private static function withdrawCom(Model $withdraw, Collection|Rocket $result): void
    {
        $withdraw->result = $result;
        $withdraw->status_arrival = 99;
        $withdraw->save();
        mq('withdrawQuery', $withdraw->serial_number, 30);
    }

    /**
     * testRefund
     * @param Model $model
     * @return Collection|Rocket|Model
     * @throws ContainerException
     * @throws InvalidParamsException
     * @throws ServiceNotFoundException
     * @throws Exception
     */
    public static function testRefund(Model $model): Collection|Rocket|Model
    {
        $where = ['objectType' => 'shop_order'];
        if (!is_null($model->paymentRelated->order_sn)) $where['orderSn'] = $model->paymentRelated->order_sn;
        else $where['serialNumber'] = $model->paymentRelated->serial_number;
        $count = PaymentRelatedLogic::countByWhere($where);
        if ($count === 0) return $model;
        else if ($count === 1) return self::refundCom($model);
        $orderId = PaymentRelatedLogic::valueByWhere(array_merge($where, ['statusRefund' => 1]), null, 'objectId');
        if (is_null($orderId)) return self::refundCom($model);
        $oldModel = ShopOrderReturnLogic::findByWhere('orderId', $orderId);
        if (is_null($oldModel)) return $model;
        $model->or_buy_status = $oldModel->or_buy_status;
        $model->or_sell_status = $oldModel->or_sell_status;
        $model->or_api_order_sn = $oldModel->or_api_order_sn;
        $model->or_api_status = $oldModel->or_api_status;
        $model->or_api_callback_json = $oldModel->or_api_callback_json;
        $model->or_api_time = $oldModel->or_api_time;
        $model->save();
        mq('shopOrderReturn', $model);
        return $model;
    }

    /**
     * refund
     * @param Model $model
     * @return Collection|Rocket|Model
     * @throws ContainerException
     * @throws InvalidParamsException
     * @throws ServiceNotFoundException
     */
    private static function refundCom(Model $model): Collection|Rocket|Model
    {
        if ($model->getTable() === 'shop_order_return') {
            $refund = $model->or_real_price;
            $total = $model->payment->amount;
        } else $refund = $total = $model->order->pay_price;
        if (!is_null($model->order->plug) && $model->order->plug > 0 && $model->paymentRelated->payment_method_id === 4) {
            return AmountLogic::plugReturn($model, $refund);
        } else {
            return Pay::wechat(YsdLib::configByModel($model->paymentRelated))->refund([
                'transaction_id' => $model->payment->out_trade_no,
                'out_trade_no' => $model->paymentRelated->order_sn ?? $model->paymentRelated->serial_number,
                'out_refund_no' => $model->serial_number,
                'reason' => '退款',
                'notify_url' => config('app.url') . 'wechat/notifyRefund',
                'amount' => [
                    'refund' => prod() ? (int)bcmul($refund, 100) : 1,
                    'total' => prod() ? (int)bcmul($total, 100) : 1,
                    'currency' => 'CNY',
                ],
            ]);
        }
    }
}
